﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Messaging;
using Sinacor.OMM.HealthMonitor.Entity;
using Sinacor.OMM.ConnectionManager.MSMQ;
using Sinacor.OMM.Util;
using Sinacor.OMM.HealthMonitor.Configuration;
using Sinacor.OMM.HealthMonitor.Entity.Receiver;
using Sinacor.OMM.HealthMonitor.Collector.SharedHealthDataMemoryStorage.Receiver;
using Sinacor.OMM.HealthMonitor.Collector.SharedHealthDataMemoryStorage.Router;
using Sinacor.OMM.HealthMonitor.Collector.SharedHealthDataMemoryStorage.OrderExecutor;
using Sinacor.OMM.HealthMonitor.Collector.SharedHealthDataMemoryStorage.HftProcessor;
using Sinacor.OMM.HealthMonitor.Collector.SharedHealthDataMemoryStorage.Dumper;
using PSE.Framework.Threading;

namespace Sinacor.OMM.HealthMonitor.Collector
{
    public class HealthMonitorCollector : IDisposable
    {
        private const string TRACE_FOLDER_NAME = "Monitor Sistema";

        private const int ONE_SECOND_IN_MILISECONDS = 1000;

        private static HealthMonitorCollector _healthMonitorCollector;
        private static Mutex _mutex;

        private MsmqSender _msmqSender;
        private Dictionary<HealthType, HealthEntity> _dctSubscribers;

        public delegate object HealthRequestListener();

        private int _timeToRefresh;
        //private int _timeToRetryConnection;
        private string _msmqConnectionString;
        private bool _keepRunning;

        private TraceWriter _traceWriter;

        private HealthMonitorCollector()
        {
            _dctSubscribers = new Dictionary<HealthType, HealthEntity>();

            _traceWriter = Tracer.GetInstance(TRACE_FOLDER_NAME, "Coletor");

            LoadConfiguration();

            Thread thr = new Thread(new ThreadStart(MonitorStart));
            thr.Start();
        }

        public static HealthMonitorCollector GetInstance()
        {
            
            if(_mutex == null)
                _mutex = new Mutex();

            _mutex.WaitOne();
            if (_healthMonitorCollector == null)
            {
                _healthMonitorCollector = new HealthMonitorCollector();
            }
            _mutex.ReleaseMutex();

            return _healthMonitorCollector;
        }

        public void AddHealthRequestListener(HealthRequestListener listener, HealthType healthType)
        {
            HealthEntity healthEntity = new HealthEntity();
            healthEntity.HealthType = healthType;
            healthEntity.Listener = listener;

            if (_dctSubscribers != null)
            {
                lock (_dctSubscribers)
                {
                    if (!_dctSubscribers.ContainsKey(healthEntity.HealthType))
                        _dctSubscribers.Add(healthType, healthEntity);
                    else
                        _dctSubscribers[healthEntity.HealthType] = healthEntity;
                }
            }
            else
            {
                throw new InvalidOperationException("Dicionário de Subscribers está null em AddHealthRequestListener");
            }
        }

        public void RemoveHealthRequestListener(HealthType healthType)
        {
            if (_dctSubscribers != null)
            {
                lock (_dctSubscribers)
                {
                    if (_dctSubscribers.ContainsKey(healthType))
                        _dctSubscribers.Remove(healthType);
                }
            }
            else
            {
                throw new InvalidOperationException("Dicionário de Subscribers está null em RemoveHealthRequestListener");
            }
        }

        private void LoadConfiguration()
        {
            _timeToRefresh = HealthMonitor.Configuration.HealthMonitorConfigurationManager.HealthMonitorConfiguration.TimeToRefreshMonitorSeconds;
            _timeToRefresh = _timeToRefresh * ONE_SECOND_IN_MILISECONDS; // Calcula em milisegundos

            _traceWriter.Trace("Intervalo de tempo para coleta de HealthStatus: " + _timeToRefresh + " milisegundos");

            //_timeToRetryConnection = HealthMonitor.Configuration.HealthMonitorConfigurationManager.HealthMonitorConfiguration.TimeToRetryConnectionSeconds;
            //_timeToRetryConnection = _timeToRetryConnection * ONE_SECOND_IN_MILISECONDS; // Calcula em milisegundos
        }

        private void MonitorStart()
        {
            _msmqSender = new MSMQHelper().CreateRepository(HealthMonitor.Configuration.HealthMonitorConfigurationManager.HealthMonitorConfiguration.MSMQHealthMonitorQueue);
            //_msmqSender = new MsmqSender(@"FormatName:Direct=OS:WSPDBB1844\Private$\HealthMonitorQueue", OMM.Logging.LogSources.HealthMonitorService,int.MaxValue,long.MaxValue);
            _keepRunning = true;

            _traceWriter.Trace("Entrou na Thread que coleta os dados de saúde do sistema");
            while (_keepRunning)
            {
                try
                {
                    RequestHealth();
                    _traceWriter.Trace("Coletou dados do sistema com sucesso...", TraceType.Optional);
                }
                catch (Exception ex)
                {
                    _traceWriter.Trace("Falhou ao coletar dados do sistema...", TraceType.Optional);
                    _traceWriter.Trace(ex.Message);
                    _traceWriter.Trace(ex.StackTrace);
                }
                Sleeper.Sleep(_timeToRefresh, IsRunning);
            }

            _traceWriter.Trace("Saiu da Thread que coleta os dados de saúde do sistema");
        }

        private bool IsRunning()
        {
            return _keepRunning;
        }

        private void RequestHealth()
        {
            if (_dctSubscribers != null)
            {
                lock (_dctSubscribers)
                {
                    if (_dctSubscribers.Count > 0)
                    {
                        Message msg = new Message();

                        // Lê o dicionário de Listeners
                        foreach (HealthEntity he in _dctSubscribers.Values)
                        {
                            object healthData = null;

                            try
                            {
                                // Chama a função que foi setada como listener e deve retornar um HealthData
                                healthData = he.Listener();
                            }
                            catch (Exception ex)
                            {
                                _traceWriter.Trace("Erro ao chamar Listener, verifique o código do serviço que está consumindo o Collecor");
                                _traceWriter.Trace(ex.Message);
                                _traceWriter.Trace(ex.StackTrace);
                            }

                            if (healthData != null)
                            {
                                if (IsKnownType(healthData))
                                {
                                    if (healthData != null)
                                    {
                                        msg.Label = ((int)he.HealthType).ToString();
                                        msg.Body = healthData;

                                        //As mensagens de HealthStatus não devem ser válidas por mais de 1 minuto
                                        msg.TimeToBeReceived = new TimeSpan(0, 0, 7);

                                        if (!_msmqSender.Send(msg))
                                        {
                                            _traceWriter.Trace("Não conseguiu enviar STATUS para a fila MSMQ " + _msmqSender.Path);
                                            _traceWriter.Trace("_msmqSender.LastErrorCode...: " + _msmqSender.LastErrorCode);
                                            _traceWriter.Trace("_msmqSender.LastErrorMessage: " + _msmqSender.LastErrorMessage);
                                        }
                                    }
                                    else
                                    {
                                        _traceWriter.Trace("Listener Retornou null para o HealthMonitorCollector");
                                    }
                                }
                                else
                                {
                                    _traceWriter.Trace("Tipo de objeto retornado para o HealthMonitorCollector não é de um tipo conhecido");
                                    _traceWriter.Trace("Tipo Retornado...: " + healthData.GetType().AssemblyQualifiedName);
                                }

                            }
                            else
                            {
                                _traceWriter.Trace("Health Data coletado está nulo");
                            }
                        }
                    }
                    else
                    {
                        _traceWriter.Trace("Dicionário de Subscribers está null em RequestHealth");
                    }
                }
            }
        }

        #region IDisposable Members

        private bool IsKnownType(object healthData)
        {
            if (typeof(ReceiverShdms) == healthData.GetType())
                return true;

            if (typeof(RouterShdms) == healthData.GetType())
                return true;

            if (typeof(OrderExecutorShdms) == healthData.GetType())
                return true;

            if (typeof(HftProcessorShdms) == healthData.GetType())
                return true;

            if (typeof(DumperShdms) == healthData.GetType())
                return true;            
            
            return false;
        }

        public void Dispose()
        {
            _keepRunning = false;

            if (_msmqSender != null)
            {
                _msmqSender.Dispose();
            }

            if (_dctSubscribers != null)
            {
                lock (_dctSubscribers)
                    _dctSubscribers.Clear();

            }

            if (_mutex != null)
            {
                _mutex.Close();
            }

            _mutex = null;
            _dctSubscribers = null; 
            _msmqSender = null;
            _healthMonitorCollector = null;
        }

        #endregion

        private class HealthEntity
        {
            HealthType _healthType;
            public HealthType HealthType
            {
                get { return _healthType; }
                set { _healthType = value; }
            }

            HealthRequestListener _healthRequestListener;
            public HealthRequestListener Listener
            {
                get { return _healthRequestListener; }
                set { _healthRequestListener = value; }
            }
        }
    }
}
